//! 简单的 WASM 辅助库：AES-GCM 加解密 + localStorage 辅助
//!
//! 说明：
//! - 使用 Rust 原生 `aes-gcm` 实现 AES-GCM 加解密，避免直接依赖浏览器 SubtleCrypto，从而简化在 wasm 环境下的实现和可测性；
//! - 使用 `web-sys` 的 `Crypto.getRandomValues` 来生成随机 IV（12 字节），以确保在浏览器环境中使用安全随机数；
//! - 提供的 API 通过 `wasm-bindgen` 导出，供 JavaScript 在浏览器中调用并由用户手动验证。

use js_sys::Object;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::window;

use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce}; // AES-GCM 256
use base64::{Engine as _, engine::general_purpose};

// 所有对外可见的说明与注释必须使用简体中文（项目规范要求）

/// 生成一个 32 字节（256-bit）的密钥并以 base64 返回
/// 返回值：base64 编码的密钥字符串
#[wasm_bindgen]
pub fn generate_key_base64() -> Result<String, JsValue> {
    // 使用浏览器的 Crypto.getRandomValues 生成随机字节
    let window = window().ok_or_else(|| JsValue::from_str("无法获取 window"))?;
    let crypto = window
        .crypto()
        .map_err(|_| JsValue::from_str("无法获取 crypto"))?;

    // 32 字节用于 AES-256
    let mut key_bytes = vec![0u8; 32];
    // 使用 Crypto.getRandomValues 填充随机字节
    crypto
        .get_random_values_with_u8_array(&mut key_bytes)
        .map_err(|e| JsValue::from(e))?;

    // 将随机字节编码为 base64 返回
    Ok(general_purpose::STANDARD.encode(&key_bytes))
}

/// 使用 base64 密钥对明文进行 AES-256-GCM 加密
/// 返回对象 { ciphertext: string(base64), iv: string(base64) }
#[allow(deprecated)]
#[wasm_bindgen]
pub fn encrypt_base64(key_b64: &str, plaintext: &str) -> Result<JsValue, JsValue> {
    // 解码密钥
    let key_bytes = general_purpose::STANDARD
        .decode(key_b64)
        .map_err(|_| JsValue::from_str("无效的 base64 密钥"))?;
    if key_bytes.len() != 32 {
        return Err(JsValue::from_str("密钥长度必须为 32 字节"));
    }

    // 构造 AES-GCM 实例
    let cipher =
        Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| JsValue::from_str("创建 Cipher 失败"))?;

    // 生成 12 字节 IV（GCM 推荐 12 字节）
    let iv_len = 12usize;
    let mut iv = vec![0u8; iv_len];
    let window = window().ok_or_else(|| JsValue::from_str("无法获取 window"))?;
    let crypto = window
        .crypto()
        .map_err(|_| JsValue::from_str("无法获取 crypto"))?;
    crypto
        .get_random_values_with_u8_array(&mut iv)
        .map_err(|e| JsValue::from(e))?;

    let nonce = Nonce::from_slice(&iv); // 96-bit nonce

    // 加密
    let ciphertext = cipher
        .encrypt(nonce, plaintext.as_bytes())
        .map_err(|_| JsValue::from_str("加密失败"))?;

    // 构造返回对象
    let obj = Object::new();
    let ct_b64 = general_purpose::STANDARD.encode(&ciphertext);
    let iv_b64 = general_purpose::STANDARD.encode(&iv);
    js_sys::Reflect::set(
        &obj,
        &JsValue::from_str("ciphertext"),
        &JsValue::from_str(&ct_b64),
    )?;
    js_sys::Reflect::set(&obj, &JsValue::from_str("iv"), &JsValue::from_str(&iv_b64))?;
    Ok(JsValue::from(obj))
}

/// 使用 base64 密钥对 base64 编码的密文和 IV 进行解密，返回明文字符串
#[allow(deprecated)]
#[wasm_bindgen]
pub fn decrypt_base64(
    key_b64: &str,
    ciphertext_b64: &str,
    iv_b64: &str,
) -> Result<String, JsValue> {
    // 解码密钥、密文和 iv
    let key_bytes = general_purpose::STANDARD
        .decode(key_b64)
        .map_err(|_| JsValue::from_str("无效的 base64 密钥"))?;
    if key_bytes.len() != 32 {
        return Err(JsValue::from_str("密钥长度必须为 32 字节"));
    }
    let ciphertext = general_purpose::STANDARD
        .decode(ciphertext_b64)
        .map_err(|_| JsValue::from_str("无效的 base64 密文"))?;
    let iv = general_purpose::STANDARD
        .decode(iv_b64)
        .map_err(|_| JsValue::from_str("无效的 base64 iv"))?;
    if iv.len() != 12 {
        return Err(JsValue::from_str("iv 长度必须为 12 字节"));
    }

    let cipher =
        Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| JsValue::from_str("创建 Cipher 失败"))?;
    let nonce = Nonce::from_slice(&iv);

    let plaintext_bytes = cipher
        .decrypt(nonce, ciphertext.as_ref())
        .map_err(|_| JsValue::from_str("解密失败"))?;

    let plaintext = String::from_utf8(plaintext_bytes)
        .map_err(|_| JsValue::from_str("解密后不是有效的 UTF-8 文本"))?;
    Ok(plaintext)
}

use js_sys;
use js_sys::Function;
use js_sys::Promise;
use wasm_bindgen::closure::Closure;
use wasm_bindgen_futures::future_to_promise;
use web_sys::{IdbDatabase, IdbOpenDbRequest};

/// 打开（或创建）IndexedDB 数据库与对象存储，返回一个 Promise，resolve 为 `IDBDatabase`
/// 若对象存储不存在，会在 onupgradeneeded 中创建该 store（键为主键，不启用 keyPath）
fn open_db_promise(db_name: &str, store_name: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();

    Promise::new(&mut |resolve, reject| {
        // 获取 indexedDB 工厂
        let idb_factory = match window().and_then(|w| w.indexed_db().ok().flatten()) {
            Some(f) => f,
            None => {
                let reject_fn: Function = reject.unchecked_into();
                let _ = reject_fn.call1(
                    &JsValue::NULL,
                    &JsValue::from_str("无法获取 indexedDB 工厂"),
                );
                return;
            }
        };

        // 打开数据库版本 1
        let open_req: IdbOpenDbRequest = match idb_factory.open_with_u32(&db_name, 1) {
            Ok(r) => r,
            Err(e) => {
                let reject_fn: Function = reject.unchecked_into();
                let _ = reject_fn.call1(&JsValue::NULL, &e);
                return;
            }
        };

        // onupgradeneeded：创建对象存储（若不存在）
        let store_name_clone = store_name.clone();
        let onupgrade = Closure::wrap(Box::new(move |evt: web_sys::Event| {
            let request: IdbOpenDbRequest = evt.target().unwrap().unchecked_into();
            let db = request.result().unwrap().unchecked_into::<IdbDatabase>();
            // 若已存在，不会重复创建
            // 在升级阶段尝试创建对象存储（若已存在，忽略错误）
            let _ = db.create_object_store(&store_name_clone);
        }) as Box<dyn FnMut(_)>);
        open_req.set_onupgradeneeded(Some(onupgrade.as_ref().unchecked_ref()));
        onupgrade.forget();

        // onsuccess：resolve 数据库对象
        let resolve_fn: Function = resolve.unchecked_into();
        let success = Closure::wrap(Box::new(move |evt: web_sys::Event| {
            let request: IdbOpenDbRequest = evt.target().unwrap().unchecked_into();
            let db = request.result().unwrap().unchecked_into::<IdbDatabase>();
            let _ = resolve_fn.call1(&JsValue::NULL, &db.into());
        }) as Box<dyn FnMut(_)>);
        open_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
        success.forget();

        // onerror：reject
        let reject_fn: Function = reject.unchecked_into();
        let error = Closure::wrap(Box::new(move |evt: web_sys::Event| {
            let request: IdbOpenDbRequest = evt.target().unwrap().unchecked_into();
            let err = request
                .error()
                .map(|e| e.into())
                .unwrap_or(JsValue::from_str("打开 IndexedDB 失败"));
            let _ = reject_fn.call1(&JsValue::NULL, &err);
        }) as Box<dyn FnMut(_)>);
        open_req.set_onerror(Some(error.as_ref().unchecked_ref()));
        error.forget();
    })
}

/// 将字符串写入指定数据库与对象存储，使用 key 作为主键，返回 Promise（resolve true）
#[wasm_bindgen]
pub fn idb_set(db_name: &str, store_name: &str, key: &str, value: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();
    let key = key.to_string();
    let value = value.to_string();

    future_to_promise(async move {
        // 打开数据库（Promise -> JsFuture -> JsValue -> IdbDatabase）
        let db_js = wasm_bindgen_futures::JsFuture::from(open_db_promise(&db_name, &store_name))
            .await
            .map_err(|e| e)?;
        let db: IdbDatabase = db_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("无法转换为 IdbDatabase"))?;

        // 开启事务并写入
        // 通过反射调用 db.transaction(storeName, "readwrite") 来避免对 web-sys 绑定名称的依赖差异
        let db_clone = db.clone();
        let db_jsval = db_clone.into();
        let tx_fn = js_sys::Reflect::get(&db_jsval, &JsValue::from_str("transaction"))?
            .dyn_into::<js_sys::Function>()?;
        let tx_js = tx_fn.call2(
            &db_jsval,
            &JsValue::from_str(&store_name),
            &JsValue::from_str("readwrite"),
        )?;
        let tx: web_sys::IdbTransaction = tx_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("转换事务失败"))?;
        let store = tx
            .object_store(&store_name)
            .map_err(|_| JsValue::from_str("获取对象存储失败"))?;

        let put_req = store
            .put_with_key(&JsValue::from_str(&value), &JsValue::from_str(&key))
            .map_err(|_| JsValue::from_str("执行 put 请求失败"))?;

        // 把 put 的完成转为 Promise
        let promise = Promise::new(&mut |resolve, reject| {
            let resolve_fn: Function = resolve.unchecked_into();
            let success = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = resolve_fn.call1(&JsValue::NULL, &JsValue::from_bool(true));
            }) as Box<dyn FnMut(_)>);
            put_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
            success.forget();

            let reject_fn: Function = reject.unchecked_into();
            let error = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = reject_fn.call1(&JsValue::NULL, &JsValue::from_str("put 请求失败"));
            }) as Box<dyn FnMut(_)>);
            put_req.set_onerror(Some(error.as_ref().unchecked_ref()));
            error.forget();
        });

        // 等待 put 完成
        wasm_bindgen_futures::JsFuture::from(promise).await?;
        Ok(JsValue::from_bool(true))
    })
}

/// 从指定数据库与对象存储中读取给定 key 的字符串值，返回 Promise，若不存在 resolve 为 null
#[wasm_bindgen]
pub fn idb_get(db_name: &str, store_name: &str, key: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();
    let key = key.to_string();

    future_to_promise(async move {
        let db_js = wasm_bindgen_futures::JsFuture::from(open_db_promise(&db_name, &store_name))
            .await
            .map_err(|e| e)?;
        let db: IdbDatabase = db_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("无法转换为 IdbDatabase"))?;

        let db_clone = db.clone();
        let db_jsval = db_clone.into();
        let tx_fn = js_sys::Reflect::get(&db_jsval, &JsValue::from_str("transaction"))?
            .dyn_into::<js_sys::Function>()?;
        let tx_js = tx_fn.call2(
            &db_jsval,
            &JsValue::from_str(&store_name),
            &JsValue::from_str("readonly"),
        )?;
        let tx: web_sys::IdbTransaction = tx_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("转换事务失败"))?;
        let store = tx
            .object_store(&store_name)
            .map_err(|_| JsValue::from_str("获取对象存储失败"))?;

        let get_req = store
            .get(&JsValue::from_str(&key))
            .map_err(|_| JsValue::from_str("执行 get 请求失败"))?;

        let promise = Promise::new(&mut |resolve, reject| {
            let resolve_fn: Function = resolve.unchecked_into();
            let success = Closure::wrap(Box::new(move |evt: web_sys::Event| {
                let request: web_sys::IdbRequest = evt.target().unwrap().unchecked_into();
                // result() 返回 Result<JsValue, JsValue>
                let result = request.result().ok();
                let out = match result {
                    Some(v) if !v.is_undefined() => v,
                    _ => JsValue::NULL,
                };
                let _ = resolve_fn.call1(&JsValue::NULL, &out);
            }) as Box<dyn FnMut(_)>);
            get_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
            success.forget();

            let reject_fn: Function = reject.unchecked_into();
            let error = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = reject_fn.call1(&JsValue::NULL, &JsValue::from_str("get 请求失败"));
            }) as Box<dyn FnMut(_)>);
            get_req.set_onerror(Some(error.as_ref().unchecked_ref()));
            error.forget();
        });

        let res = wasm_bindgen_futures::JsFuture::from(promise).await?;
        Ok(res)
    })
}

/// 从指定数据库与对象存储中删除给定 key，返回 Promise，resolve 为 true（删除成功或 key 不存在）
#[wasm_bindgen]
pub fn idb_delete(db_name: &str, store_name: &str, key: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();
    let key = key.to_string();

    future_to_promise(async move {
        let db_js = wasm_bindgen_futures::JsFuture::from(open_db_promise(&db_name, &store_name))
            .await
            .map_err(|e| e)?;
        let db: IdbDatabase = db_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("无法转换为 IdbDatabase"))?;

        // 创建只写事务并执行 delete
        let db_clone = db.clone();
        let db_jsval = db_clone.into();
        let tx_fn = js_sys::Reflect::get(&db_jsval, &JsValue::from_str("transaction"))?
            .dyn_into::<js_sys::Function>()?;
        let tx_js = tx_fn.call2(
            &db_jsval,
            &JsValue::from_str(&store_name),
            &JsValue::from_str("readwrite"),
        )?;
        let tx: web_sys::IdbTransaction = tx_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("转换事务失败"))?;
        let store = tx
            .object_store(&store_name)
            .map_err(|_| JsValue::from_str("获取对象存储失败"))?;

        let del_req = store
            .delete(&JsValue::from_str(&key))
            .map_err(|_| JsValue::from_str("执行 delete 请求失败"))?;

        let promise = Promise::new(&mut |resolve, reject| {
            let resolve_fn: Function = resolve.unchecked_into();
            let success = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = resolve_fn.call1(&JsValue::NULL, &JsValue::from_bool(true));
            }) as Box<dyn FnMut(_)>);
            del_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
            success.forget();

            let reject_fn: Function = reject.unchecked_into();
            let error = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = reject_fn.call1(&JsValue::NULL, &JsValue::from_str("delete 请求失败"));
            }) as Box<dyn FnMut(_)>);
            del_req.set_onerror(Some(error.as_ref().unchecked_ref()));
            error.forget();
        });

        wasm_bindgen_futures::JsFuture::from(promise).await?;
        Ok(JsValue::from_bool(true))
    })
}

/// 列出指定数据库与对象存储中的所有 key，返回 Promise，resolve 为 JS 数组（若为空返回空数组）
#[wasm_bindgen]
pub fn idb_keys(db_name: &str, store_name: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();

    future_to_promise(async move {
        let db_js = wasm_bindgen_futures::JsFuture::from(open_db_promise(&db_name, &store_name))
            .await
            .map_err(|e| e)?;
        let db: IdbDatabase = db_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("无法转换为 IdbDatabase"))?;

        let db_clone = db.clone();
        let db_jsval = db_clone.into();
        let tx_fn = js_sys::Reflect::get(&db_jsval, &JsValue::from_str("transaction"))?
            .dyn_into::<js_sys::Function>()?;
        let tx_js = tx_fn.call2(
            &db_jsval,
            &JsValue::from_str(&store_name),
            &JsValue::from_str("readonly"),
        )?;
        let tx: web_sys::IdbTransaction = tx_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("转换事务失败"))?;
        let store = tx
            .object_store(&store_name)
            .map_err(|_| JsValue::from_str("获取对象存储失败"))?;

        // 尝试使用 get_all_keys，如果不可用则返回空数组
        let get_keys_res = store.get_all_keys();
        let get_req = match get_keys_res {
            Ok(r) => r,
            Err(_) => return Err(JsValue::from_str("get_all_keys 不可用")),
        };

        let promise = Promise::new(&mut |resolve, reject| {
            let resolve_fn: Function = resolve.unchecked_into();
            let success = Closure::wrap(Box::new(move |evt: web_sys::Event| {
                let request: web_sys::IdbRequest = evt.target().unwrap().unchecked_into();
                let result = request.result().ok();
                let out = match result {
                    Some(v) => v,
                    None => js_sys::Array::new().into(),
                };
                let _ = resolve_fn.call1(&JsValue::NULL, &out);
            }) as Box<dyn FnMut(_)>);
            get_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
            success.forget();

            let reject_fn: Function = reject.unchecked_into();
            let error = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ =
                    reject_fn.call1(&JsValue::NULL, &JsValue::from_str("get_all_keys 请求失败"));
            }) as Box<dyn FnMut(_)>);
            get_req.set_onerror(Some(error.as_ref().unchecked_ref()));
            error.forget();
        });

        let res = wasm_bindgen_futures::JsFuture::from(promise).await?;
        Ok(res)
    })
}

/// 清空指定数据库与对象存储中的所有记录，返回 Promise，resolve 为 true（清空成功）
#[wasm_bindgen]
pub fn idb_clear(db_name: &str, store_name: &str) -> Promise {
    let db_name = db_name.to_string();
    let store_name = store_name.to_string();

    future_to_promise(async move {
        let db_js = wasm_bindgen_futures::JsFuture::from(open_db_promise(&db_name, &store_name))
            .await
            .map_err(|e| e)?;
        let db: IdbDatabase = db_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("无法转换为 IdbDatabase"))?;

        let db_clone = db.clone();
        let db_jsval = db_clone.into();
        let tx_fn = js_sys::Reflect::get(&db_jsval, &JsValue::from_str("transaction"))?
            .dyn_into::<js_sys::Function>()?;
        let tx_js = tx_fn.call2(
            &db_jsval,
            &JsValue::from_str(&store_name),
            &JsValue::from_str("readwrite"),
        )?;
        let tx: web_sys::IdbTransaction = tx_js
            .dyn_into()
            .map_err(|_| JsValue::from_str("转换事务失败"))?;
        let store = tx
            .object_store(&store_name)
            .map_err(|_| JsValue::from_str("获取对象存储失败"))?;

        let clear_req = store
            .clear()
            .map_err(|_| JsValue::from_str("执行 clear 请求失败"))?;

        let promise = Promise::new(&mut |resolve, reject| {
            let resolve_fn: Function = resolve.unchecked_into();
            let success = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = resolve_fn.call1(&JsValue::NULL, &JsValue::from_bool(true));
            }) as Box<dyn FnMut(_)>);
            clear_req.set_onsuccess(Some(success.as_ref().unchecked_ref()));
            success.forget();

            let reject_fn: Function = reject.unchecked_into();
            let error = Closure::wrap(Box::new(move |_evt: web_sys::Event| {
                let _ = reject_fn.call1(&JsValue::NULL, &JsValue::from_str("clear 请求失败"));
            }) as Box<dyn FnMut(_)>);
            clear_req.set_onerror(Some(error.as_ref().unchecked_ref()));
            error.forget();
        });

        wasm_bindgen_futures::JsFuture::from(promise).await?;
        Ok(JsValue::from_bool(true))
    })
}

// 为了遵守项目的注释规范，不要添加不确定性语言。
